/**
 * @ Author: luoqi
 * @ Create Time: 2024-04-17 22:26
 * @ Modified by: luoqi
 * @ Modified time: 2025-07-06 22:21
 * @ Description:
 */

#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <bsp.h>
#include "flash.h"
#include "cli.h"

static int _cmd_flash_hdl(int argc, char **argv);

typedef enum {
    FLASH_OPT_ERR_NONE = 0,
    FLASH_OPT_ERR_ERASE,
    FLASH_OPT_ERR_WRITE,
    FLASH_OPT_ERR_READ,
    FLASH_OPT_ERR_ADDR_UNALIGNED,
    FLASH_OPT_ERR_ADDR_OUTRANGE,
} FlashOptErr;

#define FLASH_ADDR_START    0x08000000
#define FLASH_ADDR_END      0x0806FFFF

static void _flash_opt_err_hdl(size_t addr, FlashOptErr err);

#define ADDR_FLASH_SECTOR_0     ((size_t)0x08000000) /* Base @ of Sector 0, 16 Kbytes */
#define ADDR_FLASH_SECTOR_1     ((size_t)0x08004000) /* Base @ of Sector 1, 16 Kbytes */
#define ADDR_FLASH_SECTOR_2     ((size_t)0x08008000) /* Base @ of Sector 2, 16 Kbytes */
#define ADDR_FLASH_SECTOR_3     ((size_t)0x0800C000) /* Base @ of Sector 3, 16 Kbytes */
#define ADDR_FLASH_SECTOR_4     ((size_t)0x08010000) /* Base @ of Sector 4, 64 Kbytes */
#define ADDR_FLASH_SECTOR_5     ((size_t)0x08020000) /* Base @ of Sector 5, 128 Kbytes */
#define ADDR_FLASH_SECTOR_6     ((size_t)0x08040000) /* Base @ of Sector 6, 128 Kbytes */
#define ADDR_FLASH_SECTOR_7     ((size_t)0x08060000) /* Base @ of Sector 7, 128 Kbytes */

static size_t flash_get_sector(size_t addr)
{
    size_t sector = 0;

    if((addr < ADDR_FLASH_SECTOR_1) && (addr >= ADDR_FLASH_SECTOR_0)) {
        sector = FLASH_SECTOR_0;
    } else if((addr < ADDR_FLASH_SECTOR_2) && (addr >= ADDR_FLASH_SECTOR_1)) {
        sector = FLASH_SECTOR_1;
    } else if((addr < ADDR_FLASH_SECTOR_3) && (addr >= ADDR_FLASH_SECTOR_2)) {
        sector = FLASH_SECTOR_2;
    } else if((addr < ADDR_FLASH_SECTOR_4) && (addr >= ADDR_FLASH_SECTOR_3)) {
        sector = FLASH_SECTOR_3;
    } else if((addr < ADDR_FLASH_SECTOR_5) && (addr >= ADDR_FLASH_SECTOR_4)) {
        sector = FLASH_SECTOR_4;
    } else if((addr < ADDR_FLASH_SECTOR_6) && (addr >= ADDR_FLASH_SECTOR_5)) {
        sector = FLASH_SECTOR_5;
    } else if((addr < ADDR_FLASH_SECTOR_7) && (addr >= ADDR_FLASH_SECTOR_6)) {
        sector = FLASH_SECTOR_6;
    }
    return sector;
}

int flash_init(void)
{
    int ret = 0;
    cli_add("flash", _cmd_flash_hdl, "flash command");
    return ret;
}

int flash_write(size_t addr, const void *data, size_t len)
{
    uint8_t *p = (uint8_t *)data;
    if((addr < FLASH_ADDR_START) || (addr > FLASH_ADDR_END) || (addr + len > FLASH_ADDR_END)) {
        _flash_opt_err_hdl(addr, FLASH_OPT_ERR_ADDR_OUTRANGE);
        return -1;
    }
    if(addr % 8 != 0) {
        _flash_opt_err_hdl(addr, FLASH_OPT_ERR_ADDR_UNALIGNED);
        return -1;
    }

    HAL_FLASH_Unlock();
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);
    size_t written_size = 0;
    size_t write_size = 0;
    int result = 0;
    while(written_size < len) {
        if(((addr + written_size) % 4 == 0) && (len - written_size >= 4)) {
            if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + written_size, *((size_t *)(p + written_size))) == HAL_OK) {
                if(*(size_t *)(addr + written_size) != *(size_t *)(p + written_size)) {
                    result = -1;
                    break;
                }
            } else {
                result = -1;
                break;
            }
            write_size = 4;
        } else if(((addr + written_size) % 2 == 0) && (len - written_size >= 2)) {
            if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr + written_size, *((uint16_t *)(p + written_size))) == HAL_OK) {
                if(*(uint16_t *)(addr + written_size) != *(uint16_t *)(p + written_size)) {
                    result = -1;
                    break;
                }
            } else {
                result = -1;
                break;
            }
            write_size = 2;
        } else {
            if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, addr + written_size, *((uint8_t *)(p + written_size))) == HAL_OK) {
                if(*(uint8_t *)(addr + written_size) != *(uint8_t *)(p + written_size)) {
                    result = -1;
                    break;
                }
            } else {
                result = -1;
                break;
            }
            write_size = 1;
        }
        written_size += write_size;
    }

    HAL_FLASH_Lock();
    if(result != 0) {
        _flash_opt_err_hdl(addr, FLASH_OPT_ERR_WRITE);
        return -1;
    }
    return len;
}

int flash_read(size_t addr, void *data, size_t len)
{
    uint8_t *p = (uint8_t *)data;
    if((addr < FLASH_ADDR_START) || (addr > FLASH_ADDR_END) || (addr + len > FLASH_ADDR_END)) {
        _flash_opt_err_hdl(addr, FLASH_OPT_ERR_ADDR_OUTRANGE);
        return -1;
    }
    for(size_t i = 0; i < len; i++) {
        p[i] = *(uint8_t *)(addr + i);
    }
    return 0;
}

int flash_erase(size_t addr, size_t len)
{
    if((addr < FLASH_ADDR_START) || (addr > FLASH_ADDR_END) || (addr + len > FLASH_ADDR_END)) {
        _flash_opt_err_hdl(addr, FLASH_OPT_ERR_ADDR_OUTRANGE);
        return -1;
    }

    HAL_FLASH_Unlock();

    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);
    
    FLASH_EraseInitTypeDef erase_area;
    uint32_t err = 0;
    erase_area.TypeErase = FLASH_TYPEERASE_SECTORS;
    size_t first_sector = flash_get_sector(addr);
    /* Get the number of sector to erase from 1st sector*/
    size_t nbofsectors = flash_get_sector(addr + len - 1) - first_sector + 1;
    /* Fill EraseInit structure*/
    erase_area.TypeErase = FLASH_TYPEERASE_SECTORS;
    erase_area.VoltageRange = FLASH_VOLTAGE_RANGE_3;
    erase_area.Sector = first_sector;
    erase_area.NbSectors = nbofsectors;

    if(HAL_FLASHEx_Erase(&erase_area, &err) != HAL_OK) {
        _flash_opt_err_hdl(addr, FLASH_OPT_ERR_ERASE);
        return -1;
    }

    HAL_FLASH_Lock();
    return len;
}

void _flash_opt_err_hdl(size_t addr, FlashOptErr err)
{
    HAL_FLASH_Lock();
    switch(err) {
    case FLASH_OPT_ERR_NONE:
        QSH(" #! Flash option success !\r\n");
        break;
    case FLASH_OPT_ERR_ERASE:
        QSH(" #! <0x%08X> Flash erase error !\r\n", addr);
        while(1);
        break;
    case FLASH_OPT_ERR_WRITE:
        QSH(" #! <0x%08X> Flash write error !\r\n", addr);
        while(1);
        break;
    case FLASH_OPT_ERR_READ:
        QSH(" #! <0x%08X> Flash read error !\r\n", addr);
        while(1);
        break;
    case FLASH_OPT_ERR_ADDR_OUTRANGE:
        QSH(" #! <0x%08X> Flash option out of range !\r\n", addr);
        break;
        case FLASH_OPT_ERR_ADDR_UNALIGNED:
        QSH(" #! <0x%08X> Flash write option data unaligned !\r\n", addr);
        break;
    default:
        QSH(" #! <0x%08X> Flash option error !\r\n", addr);
        break;
    }
}


static inline void _cmd_flash_help(void)
{
    QSH(" -wr <addr> <data>\r\n");
    QSH(" -rd <addr> <len>\r\n");
    QSH(" -erase <addr> <len>\r\n");
}

int _cmd_flash_hdl(int argc, char **argv)
{
    if(argc == 1) {
        _cmd_flash_help();
    } else if(ISARG(argv[1], "wr") && argc == 4) {
        size_t addr = (size_t)atoi(argv[2]) + FLASH_ADDR_START;
        int len = qstrlen(argv[3]);
        flash_erase(addr, len);
        flash_write(addr, (uint8_t *)argv[3], len);
    } else if(ISARG(argv[1], "rd") && argc == 4) {
        int len = atoi(argv[3]);
        uint8_t buf[len];
        size_t addr = (size_t)atoi(argv[2]) + FLASH_ADDR_START;
        flash_read(addr, buf, len);
        QSH("     ");
        for(int i = 0; i < 16; i++) {
            QSH(" %02X", i);
        }
        QSH("\r\n");
        size_t offset = 0;
        for(int i = 0; i < len; i++) {
            if(i % 16 == 0) {
                QSH("\r\n%04X ", offset);
                offset++;
            }
            QSH(" %02X", buf[i]);
        }
        QSH("\r\n");
    } else if(ISARG(argv[1], "erase") && argc == 4) {
        size_t addr = (size_t)atoi(argv[2]) + FLASH_ADDR_START;
        int len = atoi(argv[3]);
        flash_erase(addr, len);
    } else {
        return -1;
    }
    return 0;
}
